/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/core/SkBlendMode.h"

#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
#include "include/private/SkColorData.h"
#include "src/base/SkVx.h"
#include "src/core/SkBlendModePriv.h"
#include "src/core/SkRasterPipeline.h"
#include "src/core/SkRasterPipelineOpContexts.h"
#include "src/core/SkRasterPipelineOpList.h"

#include <optional>

bool SkBlendMode_ShouldPreScaleCoverage(SkBlendMode mode, bool rgb_coverage)
{
    // The most important things we do here are:
    //   1) never pre-scale with rgb coverage if the blend mode involves a source-alpha term;
    //   2) always pre-scale Plus.
    //
    // When we pre-scale with rgb coverage, we scale each of source r,g,b, with a distinct value,
    // and source alpha with one of those three values.  This process destructively updates the
    // source-alpha term, so we can't evaluate blend modes that need its original value.
    //
    // Plus always requires pre-scaling as a specific quirk of its implementation in
    // SkRasterPipeline.  This lets us put the clamp inside the blend mode itself rather
    // than as a separate stage that'd come after the lerp.
    //
    // This function is a finer-grained breakdown of SkBlendMode_SupportsCoverageAsAlpha().
    switch (mode) {
        case SkBlendMode::kDst:     // d              --> no sa term, ok!
        case SkBlendMode::kDstOver: // d + s*inv(da)  --> no sa term, ok!
        case SkBlendMode::kPlus:    // clamp(s+d)     --> no sa term, ok!
            return true;

        case SkBlendMode::kDstOut:  // d * inv(sa)
        case SkBlendMode::kSrcATop: // s*da + d*inv(sa)
        case SkBlendMode::kSrcOver: // s + d*inv(sa)
        case SkBlendMode::kXor:     // s*inv(da) + d*inv(sa)
            return !rgb_coverage;

        default:
            break;
    }
    return false;
}

// Users of this function may want to switch to the rgb-coverage aware version above.
bool SkBlendMode_SupportsCoverageAsAlpha(SkBlendMode mode)
{
    return SkBlendMode_ShouldPreScaleCoverage(mode, false);
}

bool SkBlendMode_AsCoeff(SkBlendMode mode, SkBlendModeCoeff *src, SkBlendModeCoeff *dst)
{
    struct CoeffRec {
        SkBlendModeCoeff fSrc;
        SkBlendModeCoeff fDst;
    };

    static constexpr CoeffRec kCoeffs[] = {
        // For Porter-Duff blend functions, color = src * src coeff + dst * dst coeff
        // src coeff                  dst coeff                     blend func
        // ----------------------     -----------------------       ----------
        { SkBlendModeCoeff::kZero,    SkBlendModeCoeff::kZero }, // clear
        { SkBlendModeCoeff::kOne,     SkBlendModeCoeff::kZero },  // src
        { SkBlendModeCoeff::kZero,    SkBlendModeCoeff::kOne  },  // dst
        { SkBlendModeCoeff::kOne,     SkBlendModeCoeff::kISA  },   // src-over
        { SkBlendModeCoeff::kIDA,     SkBlendModeCoeff::kOne  },   // dst-over
        { SkBlendModeCoeff::kDA,      SkBlendModeCoeff::kZero },   // src-in
        { SkBlendModeCoeff::kZero,    SkBlendModeCoeff::kSA   },   // dst-in
        { SkBlendModeCoeff::kIDA,     SkBlendModeCoeff::kZero },  // src-out
        { SkBlendModeCoeff::kZero,    SkBlendModeCoeff::kISA  },  // dst-out
        { SkBlendModeCoeff::kDA,      SkBlendModeCoeff::kISA  },    // src-atop
        { SkBlendModeCoeff::kIDA,     SkBlendModeCoeff::kSA   },    // dst-atop
        { SkBlendModeCoeff::kIDA,     SkBlendModeCoeff::kISA  },   // xor

        { SkBlendModeCoeff::kOne,     SkBlendModeCoeff::kOne  }, // plus
        { SkBlendModeCoeff::kZero,    SkBlendModeCoeff::kSC   }, // modulate
        { SkBlendModeCoeff::kOne,     SkBlendModeCoeff::kISC  }, // screen
    };

    if (mode > SkBlendMode::kScreen) {
        return false;
    }
    if (src) {
        *src = kCoeffs[static_cast<int>(mode)].fSrc;
    }
    if (dst) {
        *dst = kCoeffs[static_cast<int>(mode)].fDst;
    }
    return true;
}

void SkBlendMode_AppendStages(SkBlendMode mode, SkRasterPipeline *p)
{
    auto stage = SkRasterPipelineOp::srcover;
    switch (mode) {
        case SkBlendMode::kClear:
            stage = SkRasterPipelineOp::clear;
            break;
        case SkBlendMode::kSrc:
            return; // This stage is a no-op.
        case SkBlendMode::kDst:
            stage = SkRasterPipelineOp::move_dst_src;
            break;
        case SkBlendMode::kSrcOver:
            stage = SkRasterPipelineOp::srcover;
            break;
        case SkBlendMode::kDstOver:
            stage = SkRasterPipelineOp::dstover;
            break;
        case SkBlendMode::kSrcIn:
            stage = SkRasterPipelineOp::srcin;
            break;
        case SkBlendMode::kDstIn:
            stage = SkRasterPipelineOp::dstin;
            break;
        case SkBlendMode::kSrcOut:
            stage = SkRasterPipelineOp::srcout;
            break;
        case SkBlendMode::kDstOut:
            stage = SkRasterPipelineOp::dstout;
            break;
        case SkBlendMode::kSrcATop:
            stage = SkRasterPipelineOp::srcatop;
            break;
        case SkBlendMode::kDstATop:
            stage = SkRasterPipelineOp::dstatop;
            break;
        case SkBlendMode::kXor:
            stage = SkRasterPipelineOp::xor_;
            break;
        case SkBlendMode::kPlus:
            stage = SkRasterPipelineOp::plus_;
            break;
        case SkBlendMode::kModulate:
            stage = SkRasterPipelineOp::modulate;
            break;

        case SkBlendMode::kScreen:
            stage = SkRasterPipelineOp::screen;
            break;
        case SkBlendMode::kOverlay:
            stage = SkRasterPipelineOp::overlay;
            break;
        case SkBlendMode::kDarken:
            stage = SkRasterPipelineOp::darken;
            break;
        case SkBlendMode::kLighten:
            stage = SkRasterPipelineOp::lighten;
            break;
        case SkBlendMode::kColorDodge:
            stage = SkRasterPipelineOp::colordodge;
            break;
        case SkBlendMode::kColorBurn:
            stage = SkRasterPipelineOp::colorburn;
            break;
        case SkBlendMode::kHardLight:
            stage = SkRasterPipelineOp::hardlight;
            break;
        case SkBlendMode::kSoftLight:
            stage = SkRasterPipelineOp::softlight;
            break;
        case SkBlendMode::kDifference:
            stage = SkRasterPipelineOp::difference;
            break;
        case SkBlendMode::kExclusion:
            stage = SkRasterPipelineOp::exclusion;
            break;
        case SkBlendMode::kMultiply:
            stage = SkRasterPipelineOp::multiply;
            break;

        case SkBlendMode::kHue:
            stage = SkRasterPipelineOp::hue;
            break;
        case SkBlendMode::kSaturation:
            stage = SkRasterPipelineOp::saturation;
            break;
        case SkBlendMode::kColor:
            stage = SkRasterPipelineOp::color;
            break;
        case SkBlendMode::kLuminosity:
            stage = SkRasterPipelineOp::luminosity;
            break;
    }
    p->append(stage);
}

SkPMColor4f SkBlendMode_Apply(SkBlendMode mode, const SkPMColor4f &src, const SkPMColor4f &dst)
{
    // special-case simple/common modes...
    switch (mode) {
        case SkBlendMode::kClear:
            return SK_PMColor4fTRANSPARENT;
        case SkBlendMode::kSrc:
            return src;
        case SkBlendMode::kDst:
            return dst;
        case SkBlendMode::kSrcOver: {
            SkPMColor4f r;
            (skvx::float4::Load(src.vec()) + skvx::float4::Load(dst.vec()) * (1 - src.fA)).store(&r);
            return r;
        }
        default:
            break;
    }

    SkRasterPipeline_<256> p;
    SkPMColor4f src_storage = src, dst_storage = dst, res_storage;
    SkRasterPipeline_MemoryCtx src_ctx = { &src_storage, 0 }, dst_ctx = { &dst_storage, 0 },
                               res_ctx = { &res_storage, 0 };

    p.append(SkRasterPipelineOp::load_f32, &dst_ctx);
    p.append(SkRasterPipelineOp::move_src_dst);
    p.append(SkRasterPipelineOp::load_f32, &src_ctx);
    SkBlendMode_AppendStages(mode, &p);
    p.append(SkRasterPipelineOp::store_f32, &res_ctx);
    p.run(0, 0, 1, 1);
    return res_storage;
}

const char *SkBlendMode_Name(SkBlendMode bm)
{
    switch (bm) {
        case SkBlendMode::kClear:
            return "Clear";
        case SkBlendMode::kSrc:
            return "Src";
        case SkBlendMode::kDst:
            return "Dst";
        case SkBlendMode::kSrcOver:
            return "SrcOver";
        case SkBlendMode::kDstOver:
            return "DstOver";
        case SkBlendMode::kSrcIn:
            return "SrcIn";
        case SkBlendMode::kDstIn:
            return "DstIn";
        case SkBlendMode::kSrcOut:
            return "SrcOut";
        case SkBlendMode::kDstOut:
            return "DstOut";
        case SkBlendMode::kSrcATop:
            return "SrcATop";
        case SkBlendMode::kDstATop:
            return "DstATop";
        case SkBlendMode::kXor:
            return "Xor";
        case SkBlendMode::kPlus:
            return "Plus";
        case SkBlendMode::kModulate:
            return "Modulate";
        case SkBlendMode::kScreen:
            return "Screen";

        case SkBlendMode::kOverlay:
            return "Overlay";
        case SkBlendMode::kDarken:
            return "Darken";
        case SkBlendMode::kLighten:
            return "Lighten";
        case SkBlendMode::kColorDodge:
            return "ColorDodge";
        case SkBlendMode::kColorBurn:
            return "ColorBurn";
        case SkBlendMode::kHardLight:
            return "HardLight";
        case SkBlendMode::kSoftLight:
            return "SoftLight";
        case SkBlendMode::kDifference:
            return "Difference";
        case SkBlendMode::kExclusion:
            return "Exclusion";
        case SkBlendMode::kMultiply:
            return "Multiply";

        case SkBlendMode::kHue:
            return "Hue";
        case SkBlendMode::kSaturation:
            return "Saturation";
        case SkBlendMode::kColor:
            return "Color";
        case SkBlendMode::kLuminosity:
            return "Luminosity";
    }
    SkUNREACHABLE;
}

static bool just_solid_color(const SkPaint &p)
{
    return SK_AlphaOPAQUE == p.getAlpha() && !p.getColorFilter() && !p.getShader();
}

SkBlendFastPath CheckFastPath(const SkPaint &paint, bool dstIsOpaque)
{
    const auto bm = paint.asBlendMode();
    if (!bm) {
        return SkBlendFastPath::kNormal;
    }
    switch (bm.value()) {
        case SkBlendMode::kSrcOver:
            return SkBlendFastPath::kSrcOver;
        case SkBlendMode::kSrc:
            if (just_solid_color(paint)) {
                return SkBlendFastPath::kSrcOver;
            }
            return SkBlendFastPath::kNormal;
        case SkBlendMode::kDst:
            return SkBlendFastPath::kSkipDrawing;
        case SkBlendMode::kDstOver:
            if (dstIsOpaque) {
                return SkBlendFastPath::kSkipDrawing;
            }
            return SkBlendFastPath::kNormal;
        case SkBlendMode::kSrcIn:
            if (dstIsOpaque && just_solid_color(paint)) {
                return SkBlendFastPath::kSrcOver;
            }
            return SkBlendFastPath::kNormal;
        case SkBlendMode::kDstIn:
            if (just_solid_color(paint)) {
                return SkBlendFastPath::kSkipDrawing;
            }
            return SkBlendFastPath::kNormal;
        default:
            return SkBlendFastPath::kNormal;
    }
}
